package superviseur;

import java.net.*;
import java.util.*;
import superviseur.gardien.Gardiens;
import superviseur.database.*;
import superviseur.bornes.Bornes;

public class ParkingCenter
{
    public static final int CODE_EV_VEHICULE_ENTRE = 0x01;
    public static final int CODE_EV_VEHICULE_SORT  = 0x02;
    public static final int CODE_EV_VEHICULE_AUT_OFF = 0x04;

    private static ParkingCenter parkingCenter;

    private Bornes bornes;
    private Gardiens gardiens;

    private TBornes tBornes;
    private TEvenements tEvenements;
    private TUtilisateurs tUtilisateurs;
    private TParams tParams;

    private int nombreVehicules;
    private int nombreVehiculesMax;
    private boolean parkingPlein;
    private boolean alarme;
    private boolean eclairage;

    private ParkingCenter()
    {
	bornes = Bornes.getInstance();
	gardiens = Gardiens.getInstance();
    }

    public static ParkingCenter getInstance()
    {
	if (parkingCenter == null)
	{
	    parkingCenter = new ParkingCenter();
	}
	return parkingCenter;
    }

    public void init(String fichierBornes, String fichierEvenements, String utilisateursFileName, String  paramsFileName,
		     int portServerBornes, int portServerGardiens, int portServerAfficheurs)
    {
	tBornes = new TBornes(fichierBornes);
	tEvenements = new TEvenements(fichierEvenements);
	tUtilisateurs = new TUtilisateurs(utilisateursFileName);
	tParams = new TParams( paramsFileName);

	bornes.init(tBornes, portServerBornes);
	gardiens.init(portServerGardiens);

	int nVMax = tParams.getNombreVehiculesMax();
	int nV = tParams.getNombreVehicules();
	setParams(nV, nVMax);

	gardiens.initBornes(tBornes.getAllBornes());
    }

    public TBornes gettBornes()
    {
	return tBornes;
    }

    public TEvenements gettEvenements()
    {
	return tEvenements;
    }

    public TParams gettParams()
    {
	return tParams;
    }

    public TUtilisateurs gettUtilisateurs()
    {
	return tUtilisateurs;
    }


    public void setParams(int nV, int nVMax)
    {
	boolean pp = false;
	if (nV >= nVMax)
	{
	    pp = true;
	    nV = nVMax;
	}
	if (pp != parkingPlein)
	{
	    bornes.setParkingPlein(pp);
	    parkingPlein = pp;
	}
	nombreVehiculesMax = nVMax;
	nombreVehicules = nV;
	gardiens.setNombreVehiculesMax(nVMax);
	gardiens.setNombreVehicules(nV);
    }

    public int getPlacesLibres()
    {
	int result = -1;
	if (nombreVehicules <= nombreVehiculesMax)
	    result = (nombreVehiculesMax - nombreVehicules);
	return result;
    }

    public Gardiens getGardiens()
    {
	return gardiens;
    }

    public Hashtable getClientsConnected()
    {
	Hashtable h = new Hashtable();
	h.put("bornes", new Integer(bornes.getNumberBornes()));
	h.put("gardiens", new Integer(gardiens.getNumberGardiens()));
	h.put("Afficheurs", new Integer(0));  //new Integer(afficheurs.getNumberAfficheurs()));

	return h;
    }

    // *************************************************************************
    //         provenance: gardiens  destination: bornes
    // *************************************************************************

    /**
     *  Provenance: manageBornesServlet  Destination: bornes
     *  Le superviseur initialise une borne (via la page html gestion des bornes)
     *  Si le serveur de la borne est demarre, cette methode donne l'ordre a l'acces de s'initialiser avec les parametres fournis.
     *  @param numeroBorne entier correspondant au numero de la borne
     * @return vrai si borne initialisee
     */
    public boolean initialiseBorne(int numeroBorne)
    {
	return bornes.initialiseBorne(numeroBorne);
    }

    /**
     *  Provenance: gardiens  Destination: bornes
     *  Le gardien envoie un message à une borne
     * @param numeroBorne entier correspondant au numero de la borne
     * @param message chaine de caracteres du message
     */
    public void messageGardien(int numeroBorne, String message)
    {
	bornes.messageGardien(numeroBorne, message);
    }

    /**
     *  Provenance: gardiens  Destination: bornes
     *  Le gardien autorise un automobiliste à entrer dans le parking
     * @param numeroBorne entier correspondant au numero de la borne
     */
    public void autoriseEntree(int numeroBorne)
    {
	bornes.autoriseEntree(numeroBorne);
	String nomPoste = tBornes.getNom(numeroBorne);
	tEvenements.addEvenement(nomPoste, numeroBorne, false, "####", true);
    }


    /**
     *   Provenance: gardiens  Destination: bornes (toutes les bornes)
     *   Le gardien allume/éteint  l'afficheur LCD
     *   @param on vrai si allumer faux si eteindre
     */
    public void eclairageAllBornes(boolean on)
    {
	eclairage = on;
	bornes.eclairageAllBornes(on);
    }


    /**
     *   Provenance:
     *      - borne: déclenchement de l'alarme (pour l'instant simulation avec la touche *)
     *      - gardien (le gardien enclenche/arrête l'alarme)
     *   Destination: bornes: informer toutes les bornes d'un changement d'etat de l'alarme
     *   @param on vrai si demarrer alarme faux pour l'arreter
     */

    public void processAlarme(final boolean on)
    {
	alarme = on;
	bornes.processAlarme(on);
    }



    //**************************************************************************
    //              provenance: bornes    destination: bornes
    // *************************************************************************

    /**
     *   Provenance: borne   Destination: TBornes et gardiens
     *   Une borne vient d'etre cree, elle demande a s'enregistrer
     *   - ajout d'une borne (persistant et gardien)
     *   - renvoie à la borne les informations: alarme, eclairage, parking plein pour son initialisation
     *   Une borne va disparaitre: elle demande à se déenregistrer
     *   - retrait d'une borne (persistant et gardien)
     *   - les informations renvoyées dans ce cas ne sont pas utilisees par la borne
     *
     * @param nomPoste nom de l'acces
     * @param numeroBorne numéro de la borne
     * @param url de la borne
     * @param init vrai:initialisation faux: suppression
     * @return configuration de la borne (bit 0: parking plein bit 1:alarme bit 2:eclairage afficheur)
     */
    public final int enregistrementAcces(String nomPoste, int numeroBorne, String url, boolean init)
    {
	//System.out.println("enregistrementAcces nomAcces: " + nomPoste + " numeroBorne: " + numeroBorne + " url: " + url + " init: " + init);
	int numBorne = (numeroBorne >> 8) + ((numeroBorne & 1)* 50);
	if (init)
	{
	    addBorne(nomPoste, numBorne, url);
	}
	else
	{
	    suppBorne(numBorne);
	}
	// parametres dans l'ordre: parkingPlein alarme eclairage
	int stateParking = (parkingPlein? 0x1:0) + (alarme? 0x2:0) + (eclairage? 0x4:0);

	return stateParking;
    }


    /**
     *  Provenance: Bornes ou ManageBornesServlet destination: TBornes et gardiens
     *  - ajout d'une borne:
     *      le superviseur ajoute une borne via le serveur web (ManageBornesServlet)
     *      une borne veut s'enregistrer automatiquement
     *  action:
     *      memorisation persistante de la borne (TBornes)
     *      mise à jour de l'interface gardien (gardiens)
     *
     * @param nomBorne nom de la borne
     * @param numeroBorne numéro de la borne
     * @param urlBorne url de la borne
     * @return vrai si borne ajoutée
     */
    public boolean addBorne(String nomBorne, int numeroBorne, String urlBorne)
    {
	boolean result = true;
	try
	{
	    URL urlTest = new URL( urlBorne );// pour voir si url valide -> provoque exception donc pas d'ajout de borne
	    synchronized(this)
	    {
		if (tBornes.addBorne(nomBorne, numeroBorne, urlBorne))
		{
		    gardiens.addBorne(nomBorne, numeroBorne);
		}
		else result = false;
	    }
	}
	catch (MalformedURLException mfurl){ result = false;}
	return result;
    }

    /**
     *  Provenance: Bornes ou ManageBornesServlet destination: TBornes et gardiens
     *  - suppression d'une borne
     *      le superviseur retire une borne via le serveur web (ManageBornesServlet)
     *      une borne veut se déenregistrer automatiquement
     *   action:
     *      effacement de la borne (TBornes)
     *      mise à jour de l'interface gardien (gardiens)
     *
     * @param numBorne numero de la borne à supprimer
     * @return vrai si borne supprimée
     */
    public boolean suppBorne(int numBorne)
    {
	boolean result = true;
	synchronized(this)
	{
	    if ( tBornes.deleteBorneNumero(numBorne))
	    {
		gardiens.suppBorne(numBorne);
	    }
	    else result = false;
	}
	return result;
    }

    //**************************************************************************


    /**
     * Une borne demande une autorisation de passage d'un vehicule
     * Elle fournit son nom, son numero, le mode d'identificatio(clavier ou carte a puce), et le code fourni par l'utilisateur
     *
     * @param numeroBorne numero de la borne
     * @param carte vrai: carte faux:clavier
     * @param code le code d'accès
     * @return vrai si accès autorisé
     */
    public final boolean isAutorise(int numeroBorne, boolean carte, String code)
    {
	boolean result = false;
	int numBorne = (numeroBorne >> 8) + ((numeroBorne & 1)* 50);
	synchronized(this)
	{
	    if (tBornes.isBorneEnregistree(numBorne))
	    {
		result = tUtilisateurs.getAutorisation(code);
		String nomPoste = tBornes.getNom(numBorne);
		tEvenements.addEvenement(nomPoste, numBorne, carte, code, result);
	    }
	}
	return  result;
    }

    /**
     * Une borne recoit un evenement "appel gardien" de la part d'un utilisateur
     * Elle en informe les interfaces gardien
     *
     * @param numeroBorne numero de la borne
     * @return vrai (non utilisé)
     */
    public final boolean appelGardien(int numeroBorne) //throws Exception
    {
	int numBorne = (numeroBorne >> 8) + ((numeroBorne & 1)* 50);
	synchronized(this)
	{
	    if (tBornes.isBorneEnregistree(numBorne))
	    {
		gardiens.doAppel(numBorne, true);
	    }
	}
	return true;
    }


    /**
     *  Une borne informe les interfaces gardien qu'un vehicule a franchi l'acces (entree ou sortie)
     *
     * @param numeroBorne numero de la borne
     * @param codeEvenementVehicule code de l'événement
     */
    public final void evVehicule(final int numeroBorne, final int codeEvenementVehicule)
    {
	// pour incrementer/decrementer le nombre de vehicules dans le parking
	//System.out.println("vehicule " +(entree ? "entre" : "sort") + " à la borne " + numeroBorne );
	int numBorne = (numeroBorne >> 8) + ((numeroBorne & 1)* 50);
	synchronized(this)
	{
	    if (tBornes.isBorneEnregistree(numBorne))
	    {
		switch (codeEvenementVehicule)
		{
		    case CODE_EV_VEHICULE_ENTRE:
			if(!parkingPlein)
			{
			    nombreVehicules++;
			    if (nombreVehicules == nombreVehiculesMax)
			    {
				parkingPlein = true;
				bornes.setParkingPlein(true);  //on previent toutes les bornes
			    }
			    gardiens.doPassage(numBorne, nombreVehicules, true);
			    tParams.setNombreVehicules(nombreVehicules);
			}
			break;

		    case CODE_EV_VEHICULE_SORT:

			if (parkingPlein)
			{
			    parkingPlein = false;
			    bornes.setParkingPlein(false);   //on previent toutes les bornes
			}
			if (nombreVehicules > 0)
			{
			    nombreVehicules--;
			    gardiens.doPassage(numBorne, nombreVehicules, false);
			    tParams.setNombreVehicules(nombreVehicules);
			}
			else
			{
			    System.out.println("erreur: le systeme indique qu'il n'y a plus de vehicules");
			    System.out.println("dans le parking alors qu'un vehicule sort !");
			}
			break;

		    case CODE_EV_VEHICULE_AUT_OFF:
			gardiens.doAfficheAutorisation(numBorne, false);
			break;
		}
	    }
	}
    }

    /**
     * evenement alarme provenant d'une borne
     * 1) Informer les bornes qui ouvrierons toutes les barrieres
     * 2) Informer les interfaces gardien qui mettrons a jour le bouton alarme
     *
     * @param numeroBorne numéro de la borne
     * @param codeDefaut code du défaut
     * @return vrai (non utilisé)
     */
    public final boolean defautBorne(int numeroBorne, String codeDefaut)
    {
	int numBorne = (numeroBorne >> 8) + ((numeroBorne & 1)* 50);
	synchronized(this)
	{
	    if (tBornes.isBorneEnregistree(numBorne))
	    {
		gardiens.defautBorne(true, numBorne, codeDefaut);
	    }
	}
	return true;
    }


    private void destroy()
    {
	// destruction des serveurs RPC
	gardiens.destroy();
	bornes.destroy();

	//liberation des resources si necessaire
	tBornes.destroy();
	tEvenements.destroy();
	tParams.destroy();
	tUtilisateurs.destroy();
	TPassword.getInstance().destroy();
    }

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// appele par gardien principal
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    public boolean canExitApplication()
    {
	boolean noBornes = bornes.gettBornes().getAllBornes().size() == 0 ;
	boolean  noGardiens = gardiens.getNumberGardiens() == 0;
	//boolean  noAfficheurs = afficheurs.getNumberAfficheurs() == 0;
	return noBornes & noGardiens;
    }

    public void exit(int level)
    {
	destroy();
	System.out.println("Exit Superviseur");
	// le serveur web (TINIHTTPServer) n'a pas de methode shutdown
	// avec un autre serveur, on pourrait utiliser les methodes destroy des servlets
	// pour liberer les resources
	// ici, le serveur web est tue avec l'application
	System.exit(level);
    }

}